﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using System.Globalization;

namespace Ict.DataAccess.Linq
{
    /// <summary>
    /// 泛型化的查询类
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class Selectable<T> : SelectableBase, IOrderedQueryable<T>
    {
        private IQueryProvider provider = null;

        private DmlEncoder encoder = null;

        public Selectable()
        {
        }

        #region IEnumerable<T> 成员

        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            return this.Provider.Execute<IEnumerator<T>>(this.Expression);
        }

        #endregion

        #region IEnumerable 成员

        public override System.Collections.IEnumerator GetEnumerator()
        {
            return (this as IEnumerable<T>).GetEnumerator();
        }

        #endregion

        #region IQueryable 成员

        public override Type ElementType
        {
            get { return typeof(T); }
        }

        public override IQueryProvider Provider
        {
            get 
            {
                if (provider == null)
                {
                    provider = new SelectableProvider<T>();
                }
                return provider; 
            }
        }

        #endregion

        internal override void BuildSql(StringBuilder stringBuilder)
        {
            // 构建查询字段表
            stringBuilder.Append("SELECT ");
            BuildFieldList(stringBuilder);

            // 构建查询表或子查询列表
            stringBuilder.Append(" FROM ");
            BuildQueryList(stringBuilder);

            // 构建查询条件
            if (this.WhereClause != null && this.WhereClause.Count > 0)
            {
                stringBuilder.Append(" WHERE ");
                BuildCondition(stringBuilder);
            }

            // 构建排序
            if (this.OrderClause != null && this.OrderClause.Count > 0)
            {
                stringBuilder.Append(" ORDER BY ");
                BuildOrder(stringBuilder);
            }
        }

        internal override string MapField(MemberExpression memberAccess)
        {
            string memberName = memberAccess.Member.Name;
            string alias = null;
            if (this.Alias != null) alias = this.Alias + '.';

            Expression exp = memberAccess.Expression;
            if (exp.NodeType == ExpressionType.Convert)
            {
                exp = (exp as UnaryExpression).Operand;
            }
            Type t = exp.Type;
            if (t == typeof(T))
            {
                if (TableMapper<T>.QueryFieldMap.Keys.Contains(memberName))
                    return alias + TableMapper<T>.QueryFieldMap[memberName];
            }

            return null;
        }

        internal override void BuildFieldList(StringBuilder stringBuilder)
        {
            Expression expression = this.SelectClause;

            if (this.IsCountOnly)
                stringBuilder.Append(" COUNT(");
            if (this.TakeCount > 0)
            {
                stringBuilder.Append("TOP ");
                stringBuilder.Append(this.TakeCount);
                stringBuilder.Append(' ');
            }
            BuildFieldList(stringBuilder, expression);
            if (this.IsCountOnly)
                stringBuilder.Append(") ");
        }

        internal override void BuildFieldList(StringBuilder stringBuilder, Expression expression)
        {
            if (expression == null
                || expression.NodeType == ExpressionType.MemberAccess
                && (expression as MemberExpression).Type == this.ElementType)
            {
                IEnumerator<string> istr = TableMapper<T>.QueryFieldMap.Values.GetEnumerator();
                if (istr.MoveNext())
                {
                    string alias = null;
                    if (this.Alias != null) alias = this.Alias + '.';
                    do
                    {
                        stringBuilder.Append(alias);
                        stringBuilder.Append(istr.Current);
                        if (!istr.MoveNext())
                            break;
                        stringBuilder.Append(',');
                    } while (true);
                }
            }
            else if (expression.NodeType == ExpressionType.MemberAccess)
            {
                string fieldName = MapField(expression as MemberExpression);
                if (fieldName != null)
                    stringBuilder.Append(fieldName);
            }
            else if (expression.NodeType == ExpressionType.New)
            {
                IEnumerator<Expression> iarg = ((NewExpression)expression).Arguments.GetEnumerator();
                if (iarg.MoveNext())
                {
                    do
                    {
                        BuildFieldList(stringBuilder, iarg.Current);
                        if (!iarg.MoveNext())
                            break;
                        stringBuilder.Append(',');
                    } while (true);
                }
            }
            else if (expression.NodeType == ExpressionType.Lambda)
            {
                BuildFieldList(stringBuilder, (expression as LambdaExpression).Body);
            }
        }

        internal override void BuildQueryList(StringBuilder stringBuilder)
        {
            stringBuilder.Append(TableMapper<T>.QueryName);
            if (this.Alias != null)
            {
                stringBuilder.Append(' ');
                stringBuilder.Append(this.Alias);
            }
        }

        internal override void BuildOrder(StringBuilder stringBuilder)
        {
            IEnumerator<Expression> iorder = this.OrderClause.GetEnumerator();
            if (iorder.MoveNext())
            {
                do
                {
                    MethodCallExpression methodExp = (iorder.Current as MethodCallExpression);
                    string methodName = methodExp.Method.Name;
                    var func = (methodExp.Arguments[1] as UnaryExpression).Operand as LambdaExpression;

                    string fieldName = this.MapField((MemberExpression)func.Body);
                    if (fieldName != null)
                    {
                        stringBuilder.Append(fieldName);
                        if (methodName.EndsWith("Descending"))
                            stringBuilder.Append(" DESC");
                    }
                    if (!iorder.MoveNext())
                        break;
                    stringBuilder.Append(',');
                } while (true);
            }
        }

        internal override void BuildCondition(StringBuilder stringBuilder)
        {
            if (encoder == null)
                encoder = DataContext.Current.CreateSqlEncoder();

            IEnumerator<Expression> iexp = this.WhereClause.GetEnumerator();

            if (iexp.MoveNext())
            {
                do
                {
                    BuildCondition(stringBuilder, iexp.Current);
                    if (!iexp.MoveNext())
                        break;
                    stringBuilder.Append(" AND ");
                } while (true);
            }
        }

        private void BuildCondition(StringBuilder stringBuilder, Expression expression)
        {
            if (expression.NodeType == ExpressionType.AndAlso
                || expression.NodeType == ExpressionType.OrElse
                || expression.NodeType == ExpressionType.Not)
            {
                BuildLogicCondition(stringBuilder, expression);
            }
            else if (expression.NodeType == ExpressionType.Quote)
            {
                BuildCondition(stringBuilder, (expression as UnaryExpression).Operand);
            }
            else if (expression.NodeType != ExpressionType.Call)
            {
                BuildRelationCondition(stringBuilder, expression);
            }
            else if (expression.NodeType == ExpressionType.Call)
            {
                BuildMethodCallCondition(stringBuilder, expression);
            }
            else
            {
                throw new NotSupportedException("不支持的条件表达式类型：" + expression.NodeType.ToString());
            }
        }

        private void BuildMethodCallCondition(StringBuilder stringBuilder, Expression expression)
        {
            MethodCallExpression method = expression as MethodCallExpression;
            string methodName = method.Method.Name;
            if (methodName == "StartsWith")
            {
                stringBuilder.Append(MapField((method.Object as MemberExpression)));
                stringBuilder.Append(" LIKE ");
                Expression exp = method.Arguments[0];
                // var v = ExtractOperand(ref exp) as string;
                var v = OperandToString(exp);
                if (v != "NULL")
                {
                    stringBuilder.Append(v);
                    stringBuilder.Append('+');
                }
                stringBuilder.Append(encoder.EncodeString("%", true));
            }
            else if (methodName == "Contains")
            {
                stringBuilder.Append(MapField((method.Object as MemberExpression)));
                stringBuilder.Append(" LIKE ");
                Expression exp = method.Arguments[0];
                var v = "\'%\'+";
                //v += ExtractOperand(ref exp) as string;
                v = OperandToString(exp);
                if (v != "NULL")
                {
                    stringBuilder.Append(encoder.EncodeString("%", true));
                    stringBuilder.Append('+');
                    stringBuilder.Append(v);
                    stringBuilder.Append('+');
                }
                stringBuilder.Append(encoder.EncodeString("%", true));
            }
            else if (methodName == "HasFlag")
            {
                stringBuilder.Append(MapField((method.Object as MemberExpression)));
                stringBuilder.Append(" & ");
                Expression exp = method.Arguments[0];
                var v = ExtractOperand(ref exp);
                var s = encoder.EncodeValue(v);
                stringBuilder.Append(s);
                stringBuilder.Append(" = ");
                stringBuilder.Append(s);
            }
            else
                throw new NotSupportedException("不支持的方法调用：" + methodName);
        }

        private void BuildLogicCondition(StringBuilder stringBuilder, Expression expression)
        {
            if (expression.NodeType == ExpressionType.Not)
            {
                stringBuilder.Append(encoder.EncodeLogicalOperator(expression.NodeType));
                stringBuilder.Append(" (");
                BuildCondition(stringBuilder, (expression as UnaryExpression).Operand);
                stringBuilder.Append(") ");
            }
            else
            {
                stringBuilder.Append(" (");
                BuildCondition(stringBuilder, (expression as BinaryExpression).Left);
                stringBuilder.Append(") ");
                stringBuilder.Append(encoder.EncodeLogicalOperator(expression.NodeType));
                stringBuilder.Append(" (");
                BuildCondition(stringBuilder, (expression as BinaryExpression).Right);
                stringBuilder.Append(") ");
            }
        }

        private void BuildRelationCondition(StringBuilder stringBuilder, Expression expression)
        {
            if (expression is MemberExpression || expression is ConstantExpression)
            {
                BuildRelationCondition(stringBuilder, 
                    expression, ExpressionType.Equal, Expression.Constant(true));
            }
            else if (expression is BinaryExpression)
            {
                Expression left = (expression as BinaryExpression).Left;
                Expression right = (expression as BinaryExpression).Right;

                BuildRelationCondition(stringBuilder, 
                    left, expression.NodeType, right);
            }
            else if (expression is UnaryExpression)
            {
                BuildRelationCondition(stringBuilder, (expression as UnaryExpression).Operand);
            }
        }

        /// <summary>
        /// 判断某个表达式是否值表达式
        /// </summary>
        /// <param name="exp"></param>
        /// <returns></returns>
        private bool IsValueOperand(Expression exp)
        {
            if (exp.NodeType == ExpressionType.Convert)
                return IsValueOperand((exp as UnaryExpression).Operand);

            return exp.NodeType == ExpressionType.MemberAccess
                    || exp.NodeType == ExpressionType.Constant;
        }

        /// <summary>
        /// 将表达式形式的操作数转换成字符串形式
        /// </summary>
        /// <param name="exp">表达式实例</param>
        /// <returns>字符串</returns>
        private string OperandToString(Expression exp)
        {
            string result = null;

            bool bValue = IsValueOperand(exp);

            if (bValue)
            {
                object o1 = ExtractOperand(ref exp);
                if (o1 is MemberInfo)
                    result = this.MapField((MemberExpression)exp);
                else
                    result = encoder.EncodeValue(o1);
            }
            else
            {
                StringBuilder sb = new StringBuilder();
                BuildRelationCondition(sb, exp);
                result = sb.ToString();
            }

            return result;
        }

        /// <summary>
        /// 构造关系表达式条件
        /// </summary>
        /// <param name="stringBuilder"></param>
        /// <param name="left"></param>
        /// <param name="expressionType"></param>
        /// <param name="right"></param>
        private void BuildRelationCondition(StringBuilder stringBuilder, 
            Expression left, ExpressionType expressionType, Expression right)
        {
            bool bRightValue = IsValueOperand(right);

            string s1 = OperandToString(left);
            string s2 = OperandToString(right);

            string condition = encoder.EncodeExpression(expressionType, left.Type, s1, s2);

            stringBuilder.Append(condition);
        }

        private object ExtractOperand(ref Expression expression)
        {
            if (expression is MemberExpression)
            {
                Expression memberExp = (expression as MemberExpression).Expression;

                if (memberExp.NodeType == ExpressionType.Parameter
                    || memberExp.NodeType == ExpressionType.Convert
                    && (memberExp as UnaryExpression).Operand.NodeType == ExpressionType.Parameter
                    || memberExp is MemberExpression
                    && (memberExp as MemberExpression).Member.DeclaringType == typeof(T))
                {
                    return (expression as MemberExpression).Member;
                }
                else
                {
                    try
                    {
                        var v = Expression.Lambda(expression).Compile().DynamicInvoke();
                        return v;
                    }
                    catch
                    {
                        if (memberExp is MemberExpression)
                            return (expression as MemberExpression).Member;
                    }
                }
                //else if (memberExp is ConstantExpression)
                //{
                //    var v = Expression.Lambda(expression).Compile().DynamicInvoke();
                //    return v;
                //}
                //else if (memberExp is MemberExpression
                //    && (memberExp as MemberExpression).Member.DeclaringType != typeof(T))
                //{
                //    var v = ExtractOperand(ref memberExp);
                //    if (v is MemberInfo)
                //        return v;
                //    else
                //        return ((expression as MemberExpression).Member as PropertyInfo).GetValue(v, null);
                //}
                //else if (memberExp.NodeType == ExpressionType.Convert
                //    && ((memberExp as UnaryExpression).Operand.NodeType != ExpressionType.Parameter))
                //{
                //    var v = Expression.Lambda(expression).Compile().DynamicInvoke();
                //    return v;
                //}

            }
            else if (expression is ConstantExpression)
            {
                return (expression as ConstantExpression).Value;
            }
            else if (expression is UnaryExpression)
            {
                expression = (expression as UnaryExpression).Operand;
                return ExtractOperand(ref expression);
            }
            
            return null;
        }
    }




}
